﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Customize.Excel.Import
{
    public class ExcelmportV2
    {
        private static readonly Type filedAttrType = typeof(ExcelImportAttribute);

        private static Dictionary<DataColumn, PropertyInfo> CheckHeader<T>(DataTable dataTable, out int rowIndex)
        {
            var properties = typeof(T).GetProperties();
            int columnConnt = properties.Count(a => Attribute.IsDefined(a, filedAttrType));
            #region 表头检查
            var mapping = new Dictionary<DataColumn, PropertyInfo>();
            rowIndex = 0;
            foreach (DataRow row in dataTable.Rows)
            {
                foreach (PropertyInfo pinfo in properties)
                {
                    if (pinfo.IsDefined(filedAttrType, false))
                    {
                        var attributes = pinfo.GetCustomAttributes(filedAttrType, false);
                        var attr = (ExcelImportAttribute)attributes.FirstOrDefault();
                        foreach (DataColumn column in dataTable.Columns)
                        {
                            string columnText = row[column.ColumnName].ToString().Trim();
                            if (columnText == attr.HeaderText)
                            {
                                mapping.Add(column, pinfo);
                                break;
                            }
                        }
                    }
                }
                rowIndex++;
                if (mapping.Count == columnConnt)
                    break;
            }
            if (mapping.Count != columnConnt)
            {
                throw new ValidationException("导入的EXCEL与表计信息模板不一致！");
            }
            #endregion
            return mapping;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dataTable"></param>
        /// <param name="defineColumnMaps">定义需校验的列</param>
        /// <param name="rowIndex">从第几行开始为数据</param>
        /// <returns></returns>
        private static Dictionary<DataColumn, PropertyInfo> CheckHeader<T>(DataTable dataTable, IEnumerable<ExcelMap> defineColumnMaps, bool hasSameColumn, out int rowIndex)
        {
            var properties = typeof(T).GetProperties();
            //定义FieldMapAttribute的属性
            var fieldColumns = properties.Where(a => Attribute.IsDefined(a, filedAttrType)).ToList();

            var shouldCheckConnt = defineColumnMaps.Count();
            #region 表头检查
            var mapping = new Dictionary<DataColumn, PropertyInfo>();
            rowIndex = 0;
            int index = 0;

            index = 0;
            foreach (DataRow row in dataTable.Rows)
            {
                foreach (var map in defineColumnMaps)
                {
                    if (mapping.Any(c => c.Value.Name == map.FieldName))
                    {
                        continue;
                    }
                    foreach (DataColumn column in dataTable.Columns)
                    {
                        if (row[column.ColumnName] == DBNull.Value)
                        {
                            continue;
                        }
                        string columnText = row[column.ColumnName].ToString().Trim();
                        if (string.IsNullOrWhiteSpace(columnText))
                        {
                            continue;
                        }
                        index++;
                        PropertyInfo pinfo = fieldColumns.FirstOrDefault(c => c.Name == map.FieldName);
                        if (columnText == map.HeaderText
                            && pinfo != null
                            && !mapping.ContainsKey(column))
                        {
                            mapping.Add(column, pinfo);
                            break;
                        }
                    }
                }
                rowIndex++;
                if (mapping.Count == shouldCheckConnt)
                    break;
            }
            if (mapping.Count != shouldCheckConnt && !hasSameColumn)
            {
                throw new ValidationException("导入的EXCEL与表计信息模板不一致！");
            }
            Console.WriteLine(index);
            #endregion
            return mapping;
        }
        /// <summary>
        /// 将table转换成List
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="table">数据源</param>
        /// <returns>返回list对象；转换中，可能会抛出ValidatorException或ImportException</returns>        
        public static IEnumerable<T> Convert<T>(DataTable table) where T : ExcelEntity
        {
            int rowIndex;
            Dictionary<DataColumn, PropertyInfo> mapping = CheckHeader<T>(table, out rowIndex);

            int rowCount = table.Rows.Count;

            List<T> listModel = new List<T>();
            for (; rowIndex < rowCount; rowIndex++)
            {
                T model = Activator.CreateInstance<T>();
                DataRow rowdata = table.Rows[rowIndex];
                foreach (DataColumn column in table.Columns)
                {
                    PropertyInfo pinfo;
                    if (mapping.TryGetValue(column, out pinfo))
                    {

                        object value = rowdata[column];
                        List<ValidationResult> results = new List<ValidationResult>();
                        ValidationAttribute[] customAttributes = (ValidationAttribute[])pinfo.GetCustomAttributes(typeof(ValidationAttribute), true);
                        var ctx = new ValidationContext(model, null, null);
                        ctx.MemberName = pinfo.Name;
                        System.ComponentModel.DataAnnotations.Validator.TryValidateValue(value, ctx, results, customAttributes);
                        if (results.Count > 0)
                        {
                            var message = string.Join("\n", results.Select(c => c.ErrorMessage));
                            throw new ValidationException(string.Format("第{0}行，第{2}列，转换数据错误：{1}", rowIndex, message, column.Ordinal));
                        }
                        SetValue<T>(pinfo, model, value);
                    }
                }
                //Check Data
                if (!string.IsNullOrWhiteSpace(model.Error))
                {
                    throw new ApplicationException(model.Error);
                }
                listModel.Add(model);
            }
            return listModel;
        }
        /// <summary>
        /// 将Excel转换为来的DataTable转换为<see cref="T"/>的集合对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="table">目标数据</param>
        /// <param name="columnMaps">需要验证转换的属性或列</param>
        /// <returns></returns>
        public static IEnumerable<T> Convert<T>(DataTable table, IEnumerable<ExcelMap> columnMaps, bool containsSameColumn = false) where T : ExcelEntity
        {
            int rowIndex;
            Dictionary<DataColumn, PropertyInfo> mapping = CheckHeader<T>(table, columnMaps, containsSameColumn, out rowIndex);

            int rowCount = table.Rows.Count;

            List<T> listModel = new List<T>();
            for (; rowIndex < rowCount; rowIndex++)
            {
                T model = Activator.CreateInstance<T>();
                DataRow rowdata = table.Rows[rowIndex];
                foreach (DataColumn column in table.Columns)
                {
                    PropertyInfo propertyInfo;
                    if (mapping.TryGetValue(column, out propertyInfo))
                    {
                        var excelValue = rowdata[column];
                        try
                        {
                            object value = excelValue;

                            List<ValidationResult> results = new List<ValidationResult>();
                            ValidationAttribute[] customAttributes = (ValidationAttribute[])propertyInfo.GetCustomAttributes(typeof(ValidationAttribute), true);
                            var ctx = new ValidationContext(model, null, null);
                            ctx.MemberName = propertyInfo.Name;
                            System.ComponentModel.DataAnnotations.Validator.TryValidateValue(value, ctx, results, customAttributes);
                            if (results.Count > 0)
                            {
                                var message = string.Join("\n", results.Select(c => c.ErrorMessage));
                                throw new ValidationException(string.Format("第{0}行，第{2}列，转换数据错误：{1}", rowIndex, message, column.Ordinal));
                            }
                            #region 是否要将原始数据转换 ,通过字段名来连接
                            var fiedldMap = columnMaps.FirstOrDefault(c => c.FieldName == propertyInfo.Name);
                            if (fiedldMap != null && fiedldMap.Convert != null)
                            {
                                value = fiedldMap.Convert.Invoke(value == null ? "" : value.ToString());
                            }
                            #endregion
                            SetValue<T>(propertyInfo, model, value);
                        }
                        catch
                        {
                            // propertyInfo.FastSetValue(model, null);
                            SetValue<T>(propertyInfo, model, excelValue);
                        }
                    }
                }
                //Check Data
                if (!string.IsNullOrWhiteSpace(model.Error) && columnMaps.Count() == 0)
                {
                    throw new ApplicationException(string.Format("第{0}行，转换数据错误：{1}", rowIndex, model.Error));
                }
                listModel.Add(model);
            }
            return listModel;
        }

        private static void SetValue<T>(PropertyInfo pinfo, T model, object value)
        {
            var propertyType = pinfo.PropertyType;
            if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(pinfo.PropertyType);
            }

            switch (propertyType.ToString())
            {
                case "System.String"://字符串类型
                    pinfo.SetValue(model, value);
                    break;
                case "System.DateTime"://日期类型
                    if (value != DBNull.Value)
                    {
                        DateTime dateValue;
                        if (DateTime.TryParse(value.ToString(), out dateValue))
                        {
                            pinfo.SetValue(model, dateValue);
                        }
                        else
                        {
                            int passDay;
                            if (int.TryParse(value.ToString(), out passDay))
                            {
                                DateTime OADate = new DateTime(1900, 1, 1);
                                dateValue = OADate.AddDays(passDay);
                                pinfo.SetValue(model, dateValue);
                            }
                        }
                    }
                    break;
                case "System.Boolean"://布尔型
                    if (value != DBNull.Value)
                    {
                        bool boolValue = false;
                        bool.TryParse(value.ToString(), out boolValue);
                        pinfo.SetValue(model, boolValue);
                    }
                    break;
                case "System.Int16"://整型
                case "System.Int32":
                case "System.Int64":
                case "System.Byte":
                    if (value != DBNull.Value)
                    {
                        int intValue = 0;
                        if (int.TryParse(value.ToString(), out intValue))
                        {
                            pinfo.SetValue(model, intValue);
                        }
                    }
                    else
                    {
                        pinfo.SetValue(model, 0);
                    }
                    break;
                case "System.Decimal"://浮点型
                    if (value != DBNull.Value)
                    {
                        decimal decValue = 0;
                        if (decimal.TryParse(value.ToString(), out decValue))
                        {
                            pinfo.SetValue(model, decValue);
                        }
                    }
                    else
                    {
                        pinfo.SetValue(model, 0);
                    }
                    break;
                case "System.Double":
                    if (value != DBNull.Value)
                    {
                        double doubValue = 0;
                        if (double.TryParse(value.ToString(), out doubValue))
                        {
                            pinfo.SetValue(model, doubValue);
                        }
                    }
                    else
                    {
                        pinfo.SetValue(model, 0);
                    }
                    break;
                default:
                    pinfo.SetValue(model, null);
                    break;
            }
        }
    }
}
